﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Linq.Dynamic.Core;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLinqExpressionTreeBuilder();

            //PreOrderBinaryTreeTest();
            //InOrderBinaryTreeTest();
            //BindaryTreeToExpressionTreeTest();
            //StringExpression();
            //StringToBinaryTree();
            //var expDemo = new ExpDemo();
            //var exp1 = new string[] { "(", "1222", "+", "2", ")", "*", "(", "3", "-", "5", ")", "*", "(", "3", "-", "5", ")" };
            //var exp2 = new string[] { "(", "age", "=", "1", ")", "&", "(", "bbb", "=", "22", ")" };
            //var str = "(age=11.1)&(Department=test)&(T=true)";

            //var list = new List<string>();

            //var chars = str.ToCharArray();
            //for (var i = 0; i < chars.Length; i++)
            //{
            //    var item = chars[i];
            //    if (item == '(' || item == ')' || item == '&' || item == '|' || item == '=')
            //    {
            //        list.Add(item.ToString());
            //    }
            //    else if (char.IsLetter(item) || char.IsDigit(item) || item == '.')
            //    {
            //        //list.Add(item.ToString());
            //        var j = i + 1;
            //        var tmp = item.ToString();
            //        while (char.IsDigit(chars[j]) || char.IsLetter(chars[j]) || chars[j] == '.')
            //        {
            //            tmp += chars[j].ToString();
            //            j++;
            //        }
            //        list.Add(tmp);
            //        i = j - 1;
            //    }
            //}
            //Console.WriteLine("origin: {0}", str);
            //Console.Write("string arrary: ");
            //foreach (var item in list)
            //{
            //    Console.Write(item);
            //}

            //Console.WriteLine();


            ////stringExp = "(age=1)&(bssss=2)";
            //var treeNode = expDemo.CreateBinaryTree(list.ToArray());
            ////var treeNode = expDemo.CreateBinaryTree(exp1);
            ////expDemo.Print(treeNode);

            //var obj = new { Age = 11.1, Department = "test", T = true };

            //var bt = new BinaryTree();
            //bt.Output = new StringBuilder();
            //bt.Type = obj.GetType();
            //bt.Init();

            //var exp = bt.BuildExp2(treeNode, string.Empty);
            //Console.WriteLine(exp);

            //var parameterExpression = bt.ParameterExpression;
            //var lambda = Expression.Lambda<Func<object, bool>>(exp, parameterExpression);

            //var func = lambda.Compile();
            //var result = func(obj);
            //Console.WriteLine("lambda: {0}, valid: {1}", lambda, result);

            //expDemo.Print(treeNode);


            //Console.WriteLine(char.IsLetter('c'));

        }

        private static void TestLinqExpressionTreeBuilder()
        {
            var entityDataValidator = new EntityDataValidator();
            var expression = "(age=11.1)&(Department=test)&(T=true)";
            var instance = new { Age = 11.1, Department = "test", T = true };

            var result = entityDataValidator.Validate(expression, instance);
            Console.WriteLine(result);
        }

        static void PreOrderBinaryTreeTest()
        {
            var node_A = TreeNodeData.BuildTreeNode("==");
            var node_B = TreeNodeData.BuildTreeNode("val");
            var node_C = TreeNodeData.BuildTreeNode("3");
            var node_D = TreeNodeData.BuildTreeNode("D");

            var stringExp = @"
(age == 1 && T == 2) 
&& ((age == 2) 
&& (age==3)) 
";

            node_A.LeftTreeNode = node_B;
            node_A.RightTreeNode = node_C;
            //node_B.LeftTreeNode = node_D;

            var bt = new BinaryTree();
            bt.InOrder(node_A);
        }

        static void InOrderBinaryTreeTest()
        {


            var stringExp = @"
(age == 1 && T == 2) 
&& ((age == 2) 
&& (age==3)) 
";

            var node_A = TreeNodeData.BuildTreeNode("age");
            var node_B = TreeNodeData.BuildTreeNode("==");
            var node_C = TreeNodeData.BuildTreeNode("1");
            var node_D = TreeNodeData.BuildTreeNode("&&");
            var node_1 = TreeNodeData.BuildTreeNode("Department");
            var node_2 = TreeNodeData.BuildTreeNode("==");
            var node_3 = TreeNodeData.BuildTreeNode("test");

            var root = node_D;
            root.LeftTreeNode = node_B;
            root.RightTreeNode = node_2;

            node_B.LeftTreeNode = node_A;
            node_B.RightTreeNode = node_C;

            node_2.LeftTreeNode = node_1;
            node_2.RightTreeNode = node_3;

            var bt = new BinaryTree();
            bt.Output = new StringBuilder();
            bt.Expression = Expression.Constant(true);
            bt.InOrder(root);

            Console.WriteLine("Expression: {0}", bt.Output.ToString());

            var obj = new { Age = 1, Department = "test" };
            var parameter = Expression.Parameter(typeof(object), "instance");

            var trueExp = Expression.Constant(false);
            var lambda = Expression.Lambda<Func<object, bool>>(trueExp, parameter);

            var func = lambda.Compile();
            var result = func(obj);

            Console.WriteLine("Result: {0}, Expression: {1}", result, lambda);
        }

        static void BindaryTreeToExpressionTreeTest()
        {


            var stringExp = @"
(age == 1 && T == 2) 
&& ((age == 2) 
&& (age==3)) 
";

            var node_A = TreeNodeData.BuildTreeNode("age");
            var node_B = TreeNodeData.BuildTreeNode("==");
            var node_C = TreeNodeData.BuildTreeNode("11.1");
            var node_D = TreeNodeData.BuildTreeNode("&&");
            var node_1 = TreeNodeData.BuildTreeNode("Department");
            var node_2 = TreeNodeData.BuildTreeNode("==");
            var node_3 = TreeNodeData.BuildTreeNode("test");

            var root = node_D;
            root.LeftTreeNode = node_B;
            root.RightTreeNode = node_2;

            node_B.LeftTreeNode = node_A;
            node_B.RightTreeNode = node_C;

            node_2.LeftTreeNode = node_1;
            node_2.RightTreeNode = node_3;

            var obj = new { Age = 11.1, Department = "test" };

            var bt = new BinaryTree();
            bt.Output = new StringBuilder();
            bt.Type = obj.GetType();
            bt.Init();

            bt.InOrder(root);
            Console.WriteLine("Expression: {0}", bt.Output.ToString());

            var expression = bt.BuildExp(root, string.Empty);
            Console.WriteLine(expression);

            var parameterExpression = bt.ParameterExpression;
            var lambda = Expression.Lambda<Func<object, bool>>(expression, parameterExpression);

            var func = lambda.Compile();
            var result = func(obj);
            Console.WriteLine("lambda: {0}, valid: {1}", lambda, result);
        }

        class BinaryTree
        {

            public void InOrder<T>(BinaryTreeNode<T> rootBinaryTreeNode)
            {
                if (rootBinaryTreeNode != null)
                {
                    InOrder(rootBinaryTreeNode.LeftTreeNode);
                    Visit(rootBinaryTreeNode);
                    InOrder(rootBinaryTreeNode.RightTreeNode);
                }
            }

            public void PreOrder<T>(BinaryTreeNode<T> rootBinaryTreeNode)
            {
                if (rootBinaryTreeNode != null)
                {
                    Visit(rootBinaryTreeNode);
                    PreOrder(rootBinaryTreeNode.LeftTreeNode);
                    PreOrder(rootBinaryTreeNode.RightTreeNode);
                }
            }

            public Type Type { get; set; }



            private ParameterExpression _parameterExpression;
            private string _parameterName = "instance";

            public ParameterExpression ParameterExpression
            {
                get
                {
                    return _parameterExpression;
                }
            }

            public void Init()
            {
                _parameterExpression = Expression.Parameter(typeof(object), _parameterName);
            }

            private HashSet<string> opSets = new HashSet<string>(){
            "(",")", "|","=","&","+","*","-","/"
        };
            private string ptn = @"[a-zA-Z0-9_]+";


            private bool checkIsOp(string c)
            {
                return opSets.Contains(c);
            }

            private Dictionary<string, ExpressionType> opmaps_new = new Dictionary<string, ExpressionType>() {
              {"=", ExpressionType.Equal},
              {"&", ExpressionType.And}
            };

            public Expression BuildExp2(TreeNode node, string direct)
            {
                if (node != null)
                {
                    if (checkIsOp(node.value))
                    {
                        Console.Write("(");
                    }
                    if (opmaps_new.ContainsKey(node.value))
                    {
                        var left = BuildExp2(node.Left, "left");
                        if (left is MemberExpression)
                        {
                            var parameterLeft = (MemberExpression)left;
                            var t = (PropertyInfo)parameterLeft.Member;

                            var changeData = Convert.ChangeType(node.Right.value, t.PropertyType, null);
                            node.Right.ChangedData = changeData;

                        }
                        var right = BuildExp2(node.Right, "right");

                        var expType = opmaps_new[node.value];


                        var exp = Expression.MakeBinary(expType, left, right);
                        return exp;

                    }
                    else
                    {
                        if (direct == "left")
                        {
                            var prop = Type.GetProperty(node.value, BindingFlags.Public
              | BindingFlags.Instance | BindingFlags.IgnoreCase);
                            var convertExp = Expression.Convert(_parameterExpression, Type);
                            var propExpression = Expression.MakeMemberAccess(convertExp, prop);
                            return propExpression;
                        }
                        else if (direct == "right")
                        {
                            var valueExpression = Expression.Constant(node.ChangedData);
                            return valueExpression;
                        }
                    }
                    //BuildExp2(node.Left, string.Empty);
                    //Console.Write(node.value);
                    //BuildExp2(node.Right,string.Empty);
                    if (checkIsOp(node.value))
                    {
                        Console.Write(")");
                    }

                }
                return null;
            }

            public Expression BuildExp(BinaryTreeNode<TreeNodeData> node, string direct)
            {
                if (node != null)
                {

                    if (opmaps.ContainsKey(node.Data.Data.ToString()))
                    {
                        var left = BuildExp(node.LeftTreeNode, "left");

                        if (left is MemberExpression)
                        {
                            var parameterLeft = (MemberExpression)left;
                            var t = (PropertyInfo)parameterLeft.Member;

                            var changeData = Convert.ChangeType(node.RightTreeNode.Data.ToString(), t.PropertyType, null);
                            node.RightTreeNode.Data.Data = changeData;

                        }

                        var right = BuildExp(node.RightTreeNode, "right");

                        var expType = opmaps[node.Data.Data.ToString()];



                        var exp = Expression.MakeBinary(expType, left, right);
                        return exp;
                    }
                    else
                    {
                        if (direct == "left")
                        {
                            var prop = Type.GetProperty(node.Data.Data.ToString(), BindingFlags.Public
              | BindingFlags.Instance | BindingFlags.IgnoreCase);
                            var convertExp = Expression.Convert(_parameterExpression, Type);
                            var propExpression = Expression.MakeMemberAccess(convertExp, prop);
                            return propExpression;
                        }
                        else if (direct == "right")
                        {
                            var valueExpression = Expression.Constant(node.Data.Data);
                            return valueExpression;
                        }
                    }

                }
                return null;
            }

            public StringBuilder Output { get; set; }

            public Expression Expression { get; set; }

            private HashSet<string> ops = new HashSet<string> { "==", "&&" };

            private Dictionary<string, ExpressionType> opmaps = new Dictionary<string, ExpressionType>() {
              {"==", ExpressionType.Equal},
              {"&&", ExpressionType.And}
            };

            public void Visit<T>(BinaryTreeNode<T> binaryTreeNode)
            {
                Output.Append(binaryTreeNode.Data.ToString());
                Console.WriteLine("VisitNode: " + binaryTreeNode.Data);
            }
        }

        class TreeNodeData
        {
            public object Data { get; set; }

            public override string ToString()
            {
                return string.Format("{0}", Data);
            }

            public static TreeNodeData Build(string nodeName)
            {
                return new TreeNodeData() { Data = nodeName };
            }

            public static BinaryTreeNode<TreeNodeData> BuildTreeNode(string nodeName)
            {
                var data = new TreeNodeData() { Data = nodeName };
                var treeNode = new BinaryTreeNode<TreeNodeData>() { Data = data };
                return treeNode;
            }
        }

        class BinaryTreeNode<T>
        {
            public T Data { get; set; }
            public BinaryTreeNode<T> LeftTreeNode { get; set; }
            public BinaryTreeNode<T> RightTreeNode { get; set; }


        }

        class TokenInfo
        {
            public string Token { get; set; }
            public string TokenType { get; set; }
            public int Deep { get; set; }

            public int Start { get; set; }
            public int End { get; set; }

        }

        public static void StringToBinaryTree()
        {
            var stringExpression = @"Age == 1 && Department == Test";

            var pattern = @"\s*";


            stringExpression = Regex.Replace(stringExpression, pattern, "");
            Console.WriteLine(stringExpression);

            var stack = new Stack<char>();
            var chars = stringExpression.ToCharArray();
            var segment = new StringBuilder();
            var tokens = new List<TokenInfo>();
            var recoredIndex = 0;
            for (var i = 0; i < chars.Length; i++)
            {
                var cur = chars[i];
                if (cur == '(')
                {
                    stack.Push(cur);
                }
                else if (cur == ')')
                {
                    stack.Pop();
                }
                else
                {
                    if (cur == '=')
                    {
                        if (i < chars.Length - 1)
                        {
                            var next = chars[i + 1];
                            if (next == '=')
                            {
                                var pToken = new TokenInfo()
                                {
                                    Token = segment.ToString(),
                                    TokenType = "Propperty"
                                };
                                tokens.Add(pToken);

                                var opToken = new TokenInfo
                                {
                                    TokenType = "Op",
                                    Token = cur.ToString() + next.ToString()
                                };
                                tokens.Add(opToken);

                                segment.Clear();
                            }
                        }

                    }
                    else if (cur == '&')
                    {
                        if (i < chars.Length - 1)
                        {
                            var next = chars[i + 1];
                            if (next == '&')
                            {
                                var valueToken = new TokenInfo()
                                {
                                    Token = segment.ToString(),
                                    TokenType = "Value"
                                };
                                tokens.Add(valueToken);

                                var opToken = new TokenInfo
                                {
                                    TokenType = "Op",
                                    Token = cur.ToString() + next.ToString()
                                };
                                tokens.Add(opToken);

                                segment.Clear();
                            }
                        }
                    }
                    else
                    {
                        segment.Append(cur);
                    }
                }
            }

            if (segment.Length > 0)
            {
                var valueToken = new TokenInfo()
                {
                    Token = segment.ToString(),
                    TokenType = "Property"
                };
                tokens.Add(valueToken);
            }

            foreach (var token in tokens)
            {
                Console.WriteLine("token-type: {0}, token: {1}", token.TokenType, token.Token);
            }
        }

        private static void StringExpression()
        {
            var opMap = new Dictionary<string, ExpressionType>();
            opMap.Add("==", ExpressionType.Equal);
            opMap.Add(">=", ExpressionType.GreaterThanOrEqual);
            opMap.Add("<=", ExpressionType.LessThanOrEqual);
            opMap.Add(">", ExpressionType.GreaterThan);
            opMap.Add("<", ExpressionType.LessThan);
            opMap.Add("&&", ExpressionType.And);
            opMap.Add("||", ExpressionType.Or);

            var booleanOps = new HashSet<char>() {
                '&',
                '|'
            };


            var stringExp = @"
(age == 1 && T == 2) 
&& ((age == 2) 
&& (age==3)) 
";

            var reg = @"[\s]*";
            Output("origin", stringExp);

            var clearStringExp = Regex.Replace(stringExp, reg, "");
            Output("clearString", clearStringExp);

            var charArr = clearStringExp.ToCharArray();
            var stack = new Stack<Char>();
            foreach (var c in charArr)
            {
                if (c == '(')
                {
                    stack.Push(c);
                }
                else if (c == ')')
                {
                    stack.Pop();
                }
            }

            var expIsValid = stack.Count == 0;

            stack.Clear();

            //var c1 = stack.Any();

            //Console.WriteLine(c1);

            Output("Isvalid", expIsValid);
            if (expIsValid)
            {
                stack.Clear();
                var expSB = new StringBuilder();
                var dep = 0;
                for (var i = 0; i < charArr.Length; i++)
                {
                    var curChar = charArr[i];


                    if (curChar == '(')
                    {
                        stack.Push(curChar);
                    }
                    else if (curChar == ')')
                    {
                        dep = stack.Count;
                        var str = expSB.ToString();
                        if (!string.IsNullOrEmpty(str))
                        {
                            expSB.Clear();
                            Console.WriteLine("exp: {0}, dep: {1}", str, dep);


                        }
                        stack.Pop();
                    }
                    else
                    {
                        if (booleanOps.Contains(curChar))
                        {
                            dep = stack.Count;
                            if (i + 1 < charArr.Length)
                            {
                                var nextChar = charArr[i + 1];
                                if (booleanOps.Contains(nextChar))
                                {
                                    Console.WriteLine("op: {0}, dep: {1}",
                                        curChar.ToString() + nextChar.ToString(), dep);
                                }
                            }
                        }
                        else
                        {
                            expSB.Append(curChar);
                        }


                        //if (curChar == '&')
                        //{
                        //    if (i < charArr.Length)
                        //    {
                        //        var nextChar = charArr[i + 1];
                        //        if (nextChar == '&')
                        //        {
                        //            var chars = new List<char>();
                        //            while (expStack.Any())
                        //            {
                        //                chars.Add(expStack.Pop());
                        //            }


                        //            var str = new String(chars.Reverse<char>().ToArray());
                        //            Output("chars", str + " ===dep:" + dep.ToString());
                        //        }
                        //    }

                        //}
                        //else { expStack.Push(curChar); }

                    }
                }

                //while (expSB.Any())
                //{
                //    var chars = new List<char>();
                //    while (expSB.Any())
                //    {
                //        chars.Add(expSB.Pop());
                //    }

                //    var str = new String(chars.Reverse<char>().ToArray());
                //    Output("chars", str);
                //}
            }




        }

        private static String StackToString(Stack<Char> list)
        {
            var count = list.Count;
            var chars = new char[count];
            while (list.Count > 0)
            {
                var c = list.Pop();
                var i = list.Count - 1;
                if (i < 0) i = 0;
                chars[i] = c;
            }

            var str = new String(chars);
            return str;
        }

        private static void ExpressionTestWithDynamicLinq()
        {
            var obj = new { Age = 1, Department = "test" };
            var linq = "obj.Age == 1 && obj.Department  == 'test' ";

        }

        public static void ExpressionTest2()
        {
            var obj = new { Age = 1, Department = "test" };


            var typeOfObj = obj.GetType();
            Console.WriteLine(typeOfObj);
            //typeOfObj
            //    .GetProperties()
            //    .ToList()
            //    .ForEach(p =>
            //    {
            //        Console.WriteLine(p);
            //    });

            var propOfAge = typeOfObj.GetProperty("Age", BindingFlags.Public
                | BindingFlags.Instance);
            var propOfdepartment = typeOfObj.GetProperty("Department", BindingFlags.Public
                | BindingFlags.Instance);
            //Console.WriteLine(propOfAge);

            var parameterExp = Expression.Parameter(typeof(object), "instance");
            var convertExp = Expression.Convert(parameterExp, typeOfObj);
            var agePropertyExp = Expression.MakeMemberAccess(convertExp, propOfAge);
            var departPropertExp = Expression.MakeMemberAccess(convertExp, propOfdepartment);


            var equal1Exp =
                Expression.MakeBinary(ExpressionType.Equal, agePropertyExp, Expression.Constant(1));
            var equalTestExp =
                Expression.MakeBinary(ExpressionType.Equal, departPropertExp, Expression.Constant("test"));

            var finalEqualExp = Expression.MakeBinary(ExpressionType.And, equal1Exp, equalTestExp);

            var lambda = Expression.Lambda<Func<object, Boolean>>(finalEqualExp, parameterExp);

            Console.WriteLine(finalEqualExp);
            Output("lambda", lambda);

            var func = lambda.Compile();
            var valid = func(obj);
            Output("valid", valid);
        }

        private static void ExpressionTest()
        {
            Expression<Func<int, int>> exp = val => val + 1;

            Output("Body", exp.Body);
            Output("NodeType", exp.NodeType);

            Output("Parameters", exp.Parameters);
            foreach (var p in exp.Parameters)
            {
                Output("p", p.Name);
            }
        }

        static void Output(string param, object msg)
        {
            Console.WriteLine(param + ": " + msg);

        }

        private static void FuncTest()
        {
            Func<int, int, int> add = (arg1, arg2) => arg1 + arg2;

            var sum = add(1, 1);

            Console.WriteLine("sum: " + sum);

            Console.ReadLine();
        }
    }
}
